﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Net.Mail;
using System.Collections.Specialized;
using System.Net.Sockets;
using System.Threading;
using Studio.Network.Mail;
using System.IO;

////////////
/// 
/// Duplicated to System.Net.Mail
/// By marstone@marstone.net, 2008/12/02
/// 
////////////
namespace Studio.Network.MailServer
{
    public class SmtpWorder
    {

        SmtpServer _server;

        Socket _client;

        public SmtpWorder(Socket socket, SmtpServer server)
        {
            this._client = socket;
            this._client.ReceiveTimeout = server.Settings.WORKER_RECEIVE_TIMEOUT;
            this._server = server;
        }

        public void Serve()
        {
            // unit: 100 ns
            long startTicks = DateTime.Now.Ticks;

            try
            {
                Console.WriteLine("[{0:u}]Client Connected. IP:{1}", DateTime.Now, _client.RemoteEndPoint);

                // Service Ready
                this.Send(SmtpStatus.ServiceReady, string.Format("{0} {1}", 
                    _server.Settings.DOMAIN_NAME, _server.Settings.SERVER_NAME));

                // Create the Email Entity
                Email email = new Email();

                while (_client.Connected && (DateTime.Now.Ticks - startTicks) < _server.Settings.WORKER_LIFE_TICKS)
                {
                    IList<SmtpCommand> commands = this.ReceiveCommand();

                    if (false == _client.Connected) break;

                    foreach (SmtpCommand command in commands)
                    {
                        switch (command.Verb)
                        {
                            case SmtpVerbs.HELO:
                                this.Send(SmtpStatus.Ok, _server.Settings.WELCOME);
                                break;
                            case SmtpVerbs.MAIL:
                                email.From = new EmailAddress(command.Data);
                                this.Send(SmtpStatus.Ok);
                                break;
                            case SmtpVerbs.RCPT:
                                email.To.Add(new EmailAddress(command.Data));
                                this.Send(SmtpStatus.Ok);
                                break;
                            case SmtpVerbs.DATA:
                                this.Send(SmtpStatus.StartMailInput);
                                this.ReceiveEmail(email);
                                this.PersistEmail(email);
                                email = new Email();
                                this.Send(SmtpStatus.Ok);
                                break;
                            case SmtpVerbs.QUIT:
                                this.Send(SmtpStatus.ServiceClosingTransmissionChannel);
                                _client.Close();
                                break;
                            case SmtpVerbs.NONE:
                                this.Send(SmtpStatus.CommandUnrecognized);
                                break;
                        }
                    }
                }

            }
            catch (Exception ex)
            {
                Console.WriteLine("[{0:u}][SmtpWorker]Exception:{1}", DateTime.Now, ex.Message);
            }
            finally
            {
                this._client.Close();
                Console.WriteLine("[{0:u}][SmtpWorker]Thread Completed.", DateTime.Now);
            }
        }

        void Send(SmtpStatus status)
        {
            this.Send(status, string.Empty);
        }

        void Send(SmtpStatus status, string suffix)
        {
            if (false == string.IsNullOrEmpty(suffix)) suffix = ":" + suffix;
            string response = string.Format("{0} {1}{2}\r\n", (int)status, status, suffix);
            _client.Send(_server.Settings.ENCODING.GetBytes(response));
            Console.Write("[{0:u}][Send]{1}", DateTime.Now, response);
        }

        string Receive()
        {
            Byte[] buffer = new Byte[256];
            int bytes = buffer.Length;

            string response = string.Empty;
            // while (true == _client.Connected && 0 < _client.Available)
            while (true == _client.Connected && buffer.Length == bytes)
            {
                // NOTICE: if the romote host shutdown the socket
                // This call will return 0 immediately
                // This is the unfortunate nature of TCP.
                bytes = _client.Receive(buffer, buffer.Length, SocketFlags.None);
                response += _server.Settings.ENCODING.GetString(buffer, 0, bytes);
            }
            if (string.IsNullOrEmpty(response)) _client.Close();
            return response;
        }
       
        IList<SmtpCommand> ReceiveCommand()
        {
            // Write to Console
            string received = this.Receive();
            string[] commandStrings = received.Split(new string[] { "\r\n" }, StringSplitOptions.RemoveEmptyEntries);
            IList<SmtpCommand> commands = new List<SmtpCommand>();
            foreach (string commandString in commandStrings)
            {
                if (false == string.IsNullOrEmpty(commandString))
                {
                    SmtpCommand command = new SmtpCommand(commandString);
                    commands.Add(command);
                }
            }
            if (false == received.EndsWith("\r\n")) received += "\r\n";
            Console.Write("[{0:u}][Receive]{1}", DateTime.Now, received);
            // Return
            return commands;
        }

        private void ReceiveEmail(Email email)
        {
            // Receive Raw data first
            IList<string> lines = new List<string>();
            string line = this.Receive();
            lines.Add(line);
            string content = line;
            while (false == content.EndsWith(SmtpData.TERMINATION_SYMBOL) && _client.Connected)
            {
                line = this.Receive();
                lines.Add(line);
                content += line;
            }
            // Analysis Header: We won't seperate header from body at present.
            for (int i = 0; i < lines.Count; i++)
            {
                line = lines[i];
                SmtpData data = new SmtpData(line);
                switch (data.Elmenent)
                { 
                    case EmailElements.FROM:
                        email.From = EmailAddress.ParseEmailAddress(data.Content);
                        break;
                    case EmailElements.BCC:
                        foreach (EmailAddress address in this.ParseEmailList(data.Content))
                            email.Bcc.Add(address);
                        break;
                    case EmailElements.TO:
                        foreach (EmailAddress address in this.ParseEmailList(data.Content))
                            email.To.Add(address);
                        break;
                    case EmailElements.CC:
                        foreach (EmailAddress address in this.ParseEmailList(data.Content))
                            email.CC.Add(address);
                        break;
                    case EmailElements.SUBJECT:
                        email.Subject = data.Content;
                        break;
                    case EmailElements.UNSUPPORTED:
                    case EmailElements.ATTACHMENT:
                    default:
                        break;
                }
            }
            // Body is everything.
            email.Body = content;
            Console.WriteLine("[{0:u}]Email Received:\r\n{1}", DateTime.Now, email);
        }

        IList<EmailAddress> ParseEmailList(string emails)
        {
            IList<EmailAddress> emailList = new List<EmailAddress>();
            string[] emailArray = emails.Split(",".ToCharArray());
            foreach (string email in emailArray)
            {
                try
                {
                    emailList.Add(new EmailAddress(email));
                }
                catch (Exception ex)
                {
                    System.Diagnostics.Debug.WriteLine(ex.Message);
                }
            }
            return emailList;
        }


        private void PersistEmail(Email email)
        {
            string file = Path.GetFullPath(_server.Settings.FOLDER_IN);
            string time = DateTime.Now.ToString("yyyyMMddhhmmss");
            string from = null == email.From ? "Anonymous" : email.From.Address;
            string to = "Anonymous";
            if(null != email.To && 0 < email.To.Count)
                to = email.To[0].Address;
            file += string.Format("{0}_[{1}]_[{2}].txt", time, from, to);
            email.SaveAs(file);
        }
    }
}